/*
 * EvolutionWriter.java
 *
 * Created on Jan 18, 2011, 1:28:56 PM
 *
 * Copyright (c) 2011 Artur Rataj.
 *
 * This code is distributed under the terms of the GNU Library
 * General Public License, either version 2 of the license or, at
 * your option, any later version.
 */

package pl.gliwice.iitis.spatialnn.network.io;

import java.util.*;
import java.io.*;
import pl.gliwice.iitis.spatialnn.network.AbstractTarget;

import pl.gliwice.iitis.spatialnn.network.Membrane;
import pl.gliwice.iitis.spatialnn.network.Network;
import pl.gliwice.iitis.spatialnn.network.Neuron;

/**
 * Writes the complete state of a network, at a given tick.
 * 
 * @author Artur Rataj
 */
public class NetworkWriter {
    /**
     * Format to use for writing.
     */
    public enum Format {
        TEXT,
    };
    /**
     * Indent size, in spaces.
     */
    protected final int INDENT_SIZE = 2;
    
    /**
     * Format to use.
     */
    Format format;
    /**
     * Current indent.
     */
    int indent;
    /**
     * The string of the current indent.
     */
    String indentString;
    /**
     * Current output stream.
     */
    OutputStreamWriter out;
    
    /**
     * Creates a new instance of EvolutionWriter. 
     * 
     * @param format format to use, null for the default of <code>TEXT</code>.
     */
    public NetworkWriter(Format format) {
        this.format = format;
        switch(this.format) {
            case TEXT:
                break;
                
            default:
                throw new RuntimeException("format not supported");
        }
    }
    /**
     * Changes indent level.
     * 
     * @param direction 1 for indent, -1 for unindent.
     */
    protected void indent(int direction) {
        switch(direction) {
            case -1:
                indent -= INDENT_SIZE;
                break;
                
            case 1:
                indent += INDENT_SIZE;
                break;
                
             default:
                throw new RuntimeException("invalid direction");
                
        }
        StringBuilder buffer = new StringBuilder();
        for(int i = 0; i < indent; ++i)
            buffer.append(" ");
        indentString = buffer.toString();
    }
    /**
     * Writes a line of text.
     * 
     * @param text text to write
     */
    protected void write(String text) throws IOException {
        out.write(indentString + text + "\n");
    }
    /**
     * Writes a membrane.
     * 
     * @param membrane membrane to write
     */
    protected void writeMembrane(Membrane membrane) throws IOException {
        write("membrane {");
        indent(1);
        write("key \"" + membrane.key + "\";");
        write("type \"" + membrane.type.getIOName() + "\";");
        write("loc " + membrane.locBeg.toStringExact() + ", " +
                membrane.locEnd.toStringExact() + ";");
        write("range " + membrane.expressionBeg + ", " + membrane.expressionEnd + ";");
        if(membrane.connections.length != 0) {
            write("connection {");
            indent(1);
            String s = "";
            for(AbstractTarget d : membrane.connections) {
                if(!s.isEmpty())
                    s += " ";
                s += d.toStringExact() + ",";
            }
            write(s);
            indent(-1);
            write("}");
        }
        indent(-1);
        write("}");
    }
    /**
     * Writes a neuron.
     * 
     * @param neuron neuron to write
     */
    protected void writeNeuron(Neuron neuron) throws IOException {
        write("neuron {");
        indent(1);
        write("key " + neuron.key + ";");
        write("type \"" + neuron.type.getIOName() + "\";");
        write("layer " + neuron.layerNum + ";");
        write("loc " + neuron.loc.toStringExact() + ";");
        write("forward " + neuron.forwardAngle + ";");
        if(neuron.inputs.length != 0) {
            write("in {");
            indent(1);
            String s = "";
            for(AbstractTarget d : neuron.inputs) {
                if(!s.isEmpty())
                    s += " ";
                s += d.toStringExact() + ",";
            }
            write(s);
            indent(-1);
            write("}");
        }
        if(neuron.output != null) {
            write("out {");
            indent(1);
            String s = "";
            for(AbstractTarget d : neuron.output) {
                if(!s.isEmpty())
                    s += " ";
                s += d.toStringExact() + ",";
            }
            write(s);
            indent(-1);
            write("}");
        }
        /*
        write("expression {");
        indent(1);
        String s = "";
        for(double d : neuron.expression) {
            if(!s.isEmpty())
                s += " ";
            s += d + ",";
        }
        write(s);
        indent(-1);
        write("}");
        */
        indent(-1);
        write("}");
    }
    /**
     * Writes a network's content. The membranes should be not null only
     * if the network being written is is the initial one.
     *
     * @param membranes membranes to write, sorted by key, null
     * for no membranes to write
     * @param neurons neurons to write
     */
    protected void writeContents(Collection<Membrane> membranes,
            List<Neuron> neurons) throws IOException {
        write("contents {");
        indent(1);
        if(membranes != null)
            for(Membrane membrane : membranes)
                writeMembrane(membrane);
        for(Neuron neuron : neurons)
            writeNeuron(neuron);
        indent(-1);
        write("}");
    }
    /**
     * Returns "false" or "true", depending on the argument.
     * 
     * @param value value, whose textual description is returned
     * @return a textual description
     */
    String booleanString(boolean value) {
        return Boolean.toString(value);
    }
    /**
     * Writes the network object.
     * 
     * @param evolution evolution object to save
     */
    protected void writeNetwork(Network network) throws IOException {
        write("network {");
        indent(1);
        write("size " + network.size.toStringExact() + ";");
        writeContents(network.membranes, network.neurons);
        indent(-1);
        write("}");
    }
    /**
     * Saves an evolution object to an output stream. This is a synchronized
     * non--reentrant method.
     * 
     * @param evolution evolution object to save
     * @param out output stream
     */
    public synchronized void write(Network network, OutputStream out) throws IOException {
        indent = 0;
        indentString = "";
        this.out = new OutputStreamWriter(out);
        writeNetwork(network);
        this.out.close();
    }
    /**
     * Saves an evolution object to a file with a given name.
     * This is a convenience method.
     * 
     * @param evolution evolution object to save
     * @param fileName name of the file to write to
     */
    public void write(Network network, String fileName) throws IOException {
        OutputStream out = new FileOutputStream(fileName);
        write(network, out);
        out.close();
    }
}
